/*
 * BaseRemoteInvocationMessageHandler.java   0.0.1    Nov 17, 2009
 * 
 * Copyright (c) 2009 mentalsmash.org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of the Apache License does not indicate that this project is
 * affiliated with the Apache Software Foundation.
 */
package org.mentalsmash.tazio.net.ril.impl;

import java.io.Serializable;
import java.util.Iterator;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.mentalsmash.documentation.Concurrency;
import org.mentalsmash.tazio.commons.TazioRuntimeException;
import org.mentalsmash.tazio.commons.identifiers.OID;
import org.mentalsmash.tazio.commons.timer.MonitorTimePerformances;
import org.mentalsmash.tazio.commons.timer.TimingsLog;
import org.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandler;
import org.mentalsmash.tazio.net.ril.RemoteMethod;
import org.mentalsmash.tazio.net.ril.RemoteObject;
import org.mentalsmash.tazio.net.ril.RemoteObjectProxy;
import org.mentalsmash.tazio.net.ril.RemoteObjectsRegistry;
import org.mentalsmash.tazio.net.ril.RemoteMethod.InvocationSemantics;
import org.mentalsmash.tazio.net.ril.exceptions.NoSuchRemoteObjectException;
import org.mentalsmash.tazio.net.ril.exceptions.NotSerializableOutcomeException;
import org.mentalsmash.tazio.net.ril.messages.MethodInvocationRequestMessage;
import org.mentalsmash.tazio.net.ril.messages.MethodInvocationResponseMessage;
import org.mentalsmash.tazio.net.ril.messages.RILRequestMessage;
import org.mentalsmash.tazio.net.ril.messages.RILResponseMessage;
import org.mentalsmash.tazio.net.ril.messages.RegistryContentRequestMessage;
import org.mentalsmash.tazio.net.ril.messages.RegistryContentResponseMessage;
import org.mentalsmash.tazio.net.ril.messages.RemoteObjectRequestMessage;
import org.mentalsmash.tazio.net.ril.messages.RemoteObjectResponseMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.google.inject.name.Named;

/**
 * @version 0.0.1 Nov 17, 2009
 * @author Andrea Sorbini <as@mentalsmash.org>
 * 
 */
@Concurrency(concurrentAccess = true, specification = "handling of incoming messages might be "
	+ "requested concurrently by the DispatchingLayerProxy that is using an handler, but no "
	+ "concern is raised as this class takes care only of relaying the received informations to/"
	+ "requesting the necessary operations from the appropriate components of the local invoker "
	+ "(as of now, only the RemoteObjectsRegistry) and build the corresponding response message to "
	+ "be returned by the DispatchingLayerProxy to the requesting invoker")
public class BaseRemoteInvocationMessageHandler implements
	RemoteInvocationMessageHandler, BaseRemoteInvocationMessageHandlerMBean {
    private static final Logger log = LoggerFactory
	    .getLogger(RemoteInvocationMessageHandler.class);

    // protected final RemoteInvoker invoker;

    protected final RemoteObjectsRegistry registry;
    protected final ProxyFixer fixer;

    private final String name;

    // MBean-related fields

    private final Object mbeanLock = new Object();

    private final TimingsLog tlog;
    private int receivedMethodInvocationsReq;
    public static final String TLOG_KEY_HANDLE = BaseRemoteInvocationMessageHandler.class.getName() + "#" + "handleRequest";
    private int completedMethodInvocationsReq;
    private int failedMethodInvocationsReq;
    private int receivedGroupMethodInvocationsReq;
    private int completedGroupMethodInvocationsReq;
    private int failedGroupMethodInvocationsReq;
    private int receivedRemoteObjectReq;
    private int sentRemoteObjectReq;
    private int invalidRemoteObjectReq;
    private int registryContentReq;
    
    private ObjectName mbeanName;
    public ObjectName getMBeanName(){
	return this.mbeanName;
    }

    @Inject
    public BaseRemoteInvocationMessageHandler(RemoteObjectsRegistry registry,
	    ProxyFixer fixer, @Named(RILBaseImplModule.HANDLER_NAME) String name, TimingsLog tlog) {
	this.fixer = fixer;
	this.registry = registry;
	this.name = name;
	this.tlog = tlog;
	// this.invoker = invoker;
	// this.invoker.setMessageHandler(this);
    }

    @MonitorTimePerformances
    @Override
    public RILResponseMessage handleRequest(RILRequestMessage req) {
	if (req instanceof MethodInvocationRequestMessage) {
	    return this
		    .handleMethodInvocationRequest((MethodInvocationRequestMessage) req);
	} else if (req instanceof RegistryContentRequestMessage) {
	    return this
		    .handleRegistryContentRequest((RegistryContentRequestMessage) req);
	} else if (req instanceof RemoteObjectRequestMessage) {
	    return this
		    .handleRemoteObjectRequest((RemoteObjectRequestMessage) req);
	} else {
	    return null;
	}
    }

    protected MethodInvocationResponseMessage handleMethodInvocationRequest(
	    MethodInvocationRequestMessage request) {
	
	Object outcome = null;
	boolean raisedException = false;

	OID oid = request.getRemoteObject();
	RemoteMethod method = request.getRemoteMethod();
	Serializable[] args = request.getArgs();

	
	
	boolean onGroup = request.getRemoteMethod().getSemantics().equals(
		InvocationSemantics.OnGroup);

	this.requestedMethodInvocation(onGroup);

	try {
	    if (method == null) {
		this.failedMethodInvocation(onGroup);
		throw new IllegalArgumentException("null method to invoke");
	    } else if (!method.checkArgs(args)) {
		this.failedMethodInvocation(onGroup);
		throw new IllegalArgumentException(
			"invalid arguments for method " + method);
	    }

//	    if (method.getDeclaringClass().equals(Object.class)) {
//		log.trace("Invocation request : on " + oid + " call " + method);
//	    } else {
//		log.debug("Invocation request : on " + oid + " call " + method);
//	    }

	    
	    StringBuilder argsStr = new StringBuilder();
		for (int i = 0; i < args.length; i++) {
		    argsStr.append(args[i]);
		    if (i < (args.length-1)) {
			argsStr.append(", ");
		    }
		}
		
		if (method.getDeclaringClass().equals(Object.class)) {
		    log.trace("Remote invocation requested on {} call {}", new Object[] { oid,
			    method });
		} else {
		    log.debug("Remote invocation requested on {} call {} args {}", new Object[] { oid,
			    method, argsStr.toString() });
		}
	    
	    if (args != null) {
		for (Serializable arg : args) {
		    if (arg instanceof RemoteObject) {
			// RemoteObjectsRegistry registry = this.invoker
			// .getRegistry();
			// RemoteInvocationDispatcher dispatcher = this.invoker
			// .getDispatcher();
			//
			RemoteObjectProxy proxy = ((RemoteObject) arg)
				.getProxy();
			this.fixer.fix(proxy);
			log.debug("Swapping argument "+arg+" with proxy");
			// proxy.setDispatcher(dispatcher);
			// proxy.setRegistry(registry);
		    }
		}
	    }

	    // RemoteObjectsRegistry registry = this.invoker.getRegistry();
	    outcome = registry.invokeMethod(oid, method, args);
	    // log.debug("Invocation's outcome: "+outcome);
	} catch (NoSuchRemoteObjectException e) {

	    if (method.getSemantics().equals(InvocationSemantics.OnGroup)) {
		log.warn("Received request for onGroup invocation"
			+ " on unknown object " + request.getRemoteObject());
	    } else {
		log.error("Unknown object for invocation: " + oid);
	    }

	    outcome = e;
	    raisedException = true;

	} catch (Exception e) {

	    log.error("Invocation raised an unexpected exception: "
		    + e.getMessage() + "(type: "
		    + e.getClass().getCanonicalName() + ")");

	    throw new TazioRuntimeException(e);
	}

	if (outcome != null && !(outcome instanceof Serializable)) {
	    log.error("Invocation's outcome is not serializable : " + outcome);
	    outcome = new NotSerializableOutcomeException(request);
	    raisedException = true;
	}

	if (outcome == null && method.getReturnType().equals(Void.TYPE)) {
	    log.trace("Method with void return value");
	}

	MethodInvocationResponseMessage rmsg = new MethodInvocationResponseMessage(
		request.getOID(), (Serializable) outcome, raisedException);

	if (outcome instanceof NoSuchRemoteObjectException) {
	    this.failedMethodInvocation(onGroup);
	} else {
	    this.completedMethodInvocation(onGroup);
	}

	return rmsg;
    }

    protected RegistryContentResponseMessage handleRegistryContentRequest(
	    RegistryContentRequestMessage request) {

	this.receivedRegistryContentRequest();

//	log
//		.debug("Request for registry's content [from: "
//			+ request.getSenderAddress() + ";oid: "
//			+ request.getID() + "]");

	// RemoteObjectsRegistry registry = this.invoker.getRegistry();
	OID[] content = registry.getRegisteredOIDs();

	RegistryContentResponseMessage rmsg = new RegistryContentResponseMessage(
		request.getOID(), content);

	return rmsg;
    }

    protected RemoteObjectResponseMessage handleRemoteObjectRequest(
	    RemoteObjectRequestMessage request) {
	RemoteObject ro = null;

	this.requestedRemoteObject();

//	log.debug("Remote object requested : " + request.getRequested()
//		+ " [from: " + request.getSenderAddress() + ";oid: "
//		+ request.getID() + "]");

	try {
	    // RemoteObjectsRegistry registry = this.invoker.getRegistry();
	    ro = registry.getRemoteObject(request.getRequested());
	} catch (NoSuchRemoteObjectException e) {
	    log.error("No such remote object : " + request.getRequested());
	}

	if (ro == null) {
	    this.invalidRemoteObject();
	} else {
	    this.sentRemoteObject();
	}

	RemoteObjectResponseMessage rmsg = new RemoteObjectResponseMessage(
		request.getOID(), ro);
	return rmsg;
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandlerMBean#
     * getGroupMethodInvocationRequests()
     */
    @Override
    public int getGroupMethodInvocationRequestsReceived() {
	synchronized (this.mbeanLock) {
	    return this.receivedGroupMethodInvocationsReq;
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandlerMBean#
     * getMethodInvocationRequests()
     */
    @Override
    public int getMethodInvocationRequestsReceived() {
	synchronized (this.mbeanLock) {
	    return this.receivedMethodInvocationsReq;
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandlerMBean#
     * getRegistryContentRequests()
     */
    @Override
    public int getRegistryContentRequests() {
	synchronized (this.mbeanLock) {
	    return this.registryContentReq++;
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandlerMBean#
     * getRemoteObjectRequests()
     */
    @Override
    public int getRemoteObjectRequestsServed() {
	synchronized (mbeanLock) {
	    return this.sentRemoteObjectReq;
	}
    }

    private void requestedMethodInvocation(boolean onGroup) {
	synchronized (this.mbeanLock) {
	    if (onGroup) {
		this.receivedGroupMethodInvocationsReq++;
	    } else {
		this.receivedMethodInvocationsReq++;
	    }
	}
    }

    private void failedMethodInvocation(boolean onGroup) {
	synchronized (this.mbeanLock) {
	    if (onGroup) {
		this.failedGroupMethodInvocationsReq++;
	    } else {
		this.failedMethodInvocationsReq++;
	    }
	}
    }

    private void completedMethodInvocation(boolean onGroup) {
	synchronized (this.mbeanLock) {
	    if (onGroup) {
		this.completedGroupMethodInvocationsReq++;
	    } else {
		this.completedMethodInvocationsReq++;
	    }
	}
    }

    private void requestedRemoteObject() {
	synchronized (this.mbeanLock) {
	    this.receivedRemoteObjectReq++;
	}
    }

    private void invalidRemoteObject() {
	synchronized (this.mbeanLock) {
	    this.invalidRemoteObjectReq++;
	}
    }

    private void sentRemoteObject() {
	synchronized (this.mbeanLock) {
	    this.sentRemoteObjectReq++;
	}
    }

    private void receivedRegistryContentRequest() {
	synchronized (this.mbeanLock) {
	    this.registryContentReq++;
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandlerMBean#
     * getExecutedMethodInvocationRequests()
     */
    @Override
    public int getMethodInvocationRequestsCompleted() {
	synchronized (this.mbeanLock) {
	    return this.completedMethodInvocationsReq;
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandlerMBean#
     * getFailedMethodInvocationRequests()
     */
    @Override
    public int getMethodInvocationRequestsFailed() {
	synchronized (this.mbeanLock) {
	    return this.failedMethodInvocationsReq;
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandlerMBean#
     * getInvalidRemoteObjectRequests()
     */
    @Override
    public int getRemoteObjectRequestsInvalid() {
	synchronized (this.mbeanLock) {
	    return this.invalidRemoteObjectReq;
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.net.ril.RemoteInvocationMessageHandlerMBean#
     * getReceivedRemoteObjectRequests()
     */
    @Override
    public int getRemoteObjectRequestsReceived() {
	synchronized (this.mbeanLock) {
	    return this.receivedRemoteObjectReq;
	}
    }

    /**
     * This method is used to inject an MBeanServer instance that will be used
     * to register this MBean
     */
    @Inject
    public void register(MBeanServer server) {
	try {
	    server.registerMBean(this, new ObjectName(
		    "org.mentalsmash.tazio.net.ril:type="+
		    "RemoteInvocationMessageHandler"));
	} catch (JMException e) {
	    System.err.println("exception: " + e.getClass().getCanonicalName()
		    + " " + e.getMessage());
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.net.ril.impl.BaseRemoteInvocationMessageHandlerMBean
     * #getCompletedGroupMethodInvocationRequests()
     */
    @Override
    public int getGroupMethodInvocationRequestsCompleted() {
	synchronized (this.mbeanLock) {
	    return this.completedGroupMethodInvocationsReq;
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.net.ril.impl.BaseRemoteInvocationMessageHandlerMBean
     * #getFailedGroupMethodInvocationRequests()
     */
    @Override
    public int getGroupMethodInvocationRequestsFailed() {
	synchronized (this.mbeanLock) {
	    return this.failedGroupMethodInvocationsReq;
	}
    }

    @Override
    public double getAvgHandlingTime() {
	return this.tlog.getAvgMeasure(TLOG_KEY_HANDLE);
    }

    @Override
    public long getMaxHandlingTime() {
	return this.tlog.getMaxMeasure(TLOG_KEY_HANDLE);
    }

    @Override
    public long getMinHandlingTime() {
	return this.tlog.getMinMeasure(TLOG_KEY_HANDLE);
    }

}
